package com.tmt.jpa.jpaservice.impl;

import com.tmt.annotation.parser.LazyLoadParser;
import com.tmt.helper.CopyHelper;
import com.tmt.helper.ReflectHelper;
import com.tmt.jpa.jpaservice.ITmtBaseRepo;
import com.tmt.jpa.jpaservice.ITmtJpaService;
import com.tmt.jpa.jpaservice.PropertyLoader;
import com.tmt.jpa.jpaservice.SyncUpdate;
import lombok.Getter;
import lombok.Setter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Example;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.persistence.*;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.rmi.ServerError;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Created by ThreeManTeam on 2017/6/9.
 */
@Getter
@Setter
@Transactional
public class DefaultTmtJpaService<T, ID extends Serializable> implements ITmtJpaService<T, ID> {

    private static final Logger logger = LoggerFactory.getLogger(DefaultTmtJpaService.class);

    @Getter
    private ITmtBaseRepo<T,ID> mainJpaRepository;

    @Getter
    private List<ITmtJpaService> otherJpaService;

    @Override
    @Transactional(readOnly = true)
    public boolean support(Class<T> clazz) {
        return true;
    }

    @Override
    public T create(T object) throws Exception {
        if (object == null)
            return object;

        //构建OneToMany对象关系。
        setManyToOneProperty(object);

        return mainJpaRepository.save(object);
    }

    @Override
    public List<T> createAll(List<T> objects) throws Exception {
        if (objects == null || objects.size() <= 0)
            return objects;

        //构建OneToMany对象关系。
        objects.forEach(this::setManyToOneProperty);

        return mainJpaRepository.save(objects);
    }

    @Override
    public T update(T object) throws Exception {
        if (object == null)
            return object;

        Object id = ReflectHelper.getFieldValueByAnnotation(object, Id.class, null);
        if (id == null)
            return null;

        T currentObj = mainJpaRepository.findOne((ID) id);

        if (!object.equals(currentObj)) {
            //构建OneToMany对象关系。
            setManyToOneProperty(object);

            //更新集合属性。
            updateCollectionProperty(object, currentObj);

            //简单属性赋值。
            CopyHelper.copyPropertyValueIfNotNull(object, currentObj, Id.class, null, null);
        }

        return (T) mainJpaRepository.save(currentObj);
    }

    @Override
    public List<T> updateAll(List<T> objects) throws Exception {
        if (objects == null || objects.size() <= 0)
            return objects;

        List curentObjs = mainJpaRepository.findAll(objects.stream()
                .map(x -> ReflectHelper.getFieldValueByAnnotation(x, Id.class, null))
                .filter(Objects::nonNull)
                .map(x->(ID)x)
                .collect(Collectors.toList()));
        for (T x : objects) {
            Long xId = (Long) ReflectHelper.getFieldValueByAnnotation(x, Id.class, null);
            Optional optional = curentObjs.stream().filter(y -> {
                Long yId = (Long) ReflectHelper.getFieldValueByAnnotation(y, Id.class, null);
                return Objects.equals(xId, yId);
            }).findFirst();
            if (optional.isPresent()) {
                //构建OneToMany对象关系。
                setManyToOneProperty(x);
                //更新集合属性。
                updateCollectionProperty(x, (T)optional.get());
                //简单属性赋值。
                CopyHelper.copyPropertyValueIfNotNull(x, optional.get(), Id.class, null, null);
            }
        }

        return (List<T>) mainJpaRepository.save(curentObjs);
    }

    @Override
    public void delete(T object) throws Exception {
        if (object != null)
            mainJpaRepository.delete(object);
    }

    @Override
    public void deleteAll(List<T> objects) throws Exception {
        if (objects != null && objects.size() > 0)
            mainJpaRepository.deleteInBatch(objects);
    }

    @Override
    @Transactional(readOnly = true)
    public T findById(Class<T> clazz, ID id) {
        return findById(id);
    }

    @Override
    public T findById(ID id) {
        return mainJpaRepository.findOne(id);
    }

    @Override
    @Transactional(readOnly = true)
    public List<T> findAll(Class<T> clazz, Example<T> example) {
        return example == null ? mainJpaRepository.findAll() : mainJpaRepository.findAll(example);
    }


    @Override
    @Transactional(readOnly = true)
    public <K> K loadEntity(Class<K> clazz, ID id, String lazyLoadName) {
        K entity = null;
        try {
            entity = mainJpaRepository.getEntityManager().find(clazz,id);
            String[] syncLoadProperties = LazyLoadParser.getLazyLoadProperties(clazz, lazyLoadName);
            if (entity != null && syncLoadProperties != null && syncLoadProperties.length > 0) {
                loadLazyProperties(entity, syncLoadProperties);
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return entity;
    }

    @Override
    public <K> K loadEntity(Class<K> clazz, String methodName, Object[] args, String lazyLoadName) {
        K entity = null;
        try {
            Class<?>[] paramTypes = null;
            if (args != null && args.length > 0) {
                paramTypes = new Class<?>[args.length];
                for (int i = 0; i < args.length; i++) {
                    paramTypes[i] = LazyLoadParser.getValidClass(args[i].getClass());
                }
            }
            Method method = this.getClass().getMethod(methodName, paramTypes);
            if (method != null) {
                entity = (K) method.invoke(this, args);
                String[] syncLoadProperties = LazyLoadParser.getLazyLoadProperties(clazz, lazyLoadName);
                if (entity != null && syncLoadProperties != null && syncLoadProperties.length > 0) {
                    loadLazyProperties(entity, syncLoadProperties);
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return entity;
    }

    @Override
    public <K> List<K> loadEntities(Class<K> clazz, String methodName, Object[] args, String lazyLoadName) {
        List<K> list = null;
        try {
            Class[] paramTypes = null;
            if (args != null && args.length > 0) {
                paramTypes = new Class[args.length];
                for (int i = 0; i < args.length; i++) {
                    paramTypes[i] = LazyLoadParser.getValidClass(args[i].getClass());
                    if (List.class.isAssignableFrom(paramTypes[i])) {
                        paramTypes[i] = List.class;
                    }
                }
            }
            Method method = this.getClass().getMethod(methodName, paramTypes);
            if (method != null) {
                list = (List<K>) method.invoke(this, args);
                String[] syncLoadProperties = LazyLoadParser.getLazyLoadProperties(clazz, lazyLoadName);
                if (!CollectionUtils.isEmpty(list) && syncLoadProperties != null && syncLoadProperties.length > 0) {
                    loadLazyProperties(list, syncLoadProperties);
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return list;
    }

    @Transactional(readOnly = true)
    public <K> void loadLazyProperties(K entity, String[] lazyProperties) {
        if (entity == null || lazyProperties == null || lazyProperties.length <= 0)
            return;

        PersistenceUnitUtil unitUtil = mainJpaRepository.getEntityManager().getEntityManagerFactory().getPersistenceUnitUtil();
        PropertyLoader loader = new PropertyLoaderImpl();
        loader.loadLazyProperties(entity, lazyProperties, unitUtil);
    }

    @Transactional(readOnly = true)
    public <K> void loadLazyProperties(Collection<K> entityList, String[] lazyProperties) {
        if (entityList == null || entityList.size() <= 0
                || lazyProperties == null || lazyProperties.length <= 0)
            return;

        PropertyLoader loader = new PropertyLoaderImpl();
        PersistenceUnitUtil unitUtil = mainJpaRepository.getEntityManager().getEntityManagerFactory().getPersistenceUnitUtil();
        entityList.forEach(x -> {
            try {
                loader.loadLazyProperties(x, lazyProperties, unitUtil);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }


    /**
     * 更新集合属性: 将废弃的属性对象从数据库中删除，将新增的对象保存到数据库中。
     *
     * @param object
     * @param
     */
    private void updateCollectionProperty(T object, T currentEntity) throws Exception {
        if (object.equals(currentEntity))
            return;

        Long id = (Long) ReflectHelper.getFieldValueByAnnotation(object, Id.class, null);

        List<Field> fieldList = ReflectHelper.findAllDeclaredFields(object.getClass());
        fieldList = fieldList.stream().filter(x -> x.isAnnotationPresent(SyncUpdate.class))
                .collect(Collectors.toList());

        for (Field x : fieldList) {
            //如果SyncUpdate注解标注的字段类型不是集合类型，则忽略之。
            if (!Collection.class.isAssignableFrom(x.getType()))
                return;

            //获取新的集合值。
            List<Object> newList = (List<Object>) ReflectHelper.getFieldValue(object, x.getName());

            //获取数据表现有的集合值。
            List<Object> currentList = currentEntity != null ?
                    (List<Object>) ReflectHelper.getFieldValue(currentEntity, x.getName())
                    : new ArrayList<>();

            //恢复新集合中现有数据的ID值。
            //retrieveObjectId(object, x, newList, currentList);

            SyncUpdate annotation = x.getAnnotation(SyncUpdate.class);

            //先删除废弃的资料记录。
            if (annotation.updateCollection()) {
                List<Object> deletedList = new ArrayList<>();
                for (Object x1 : currentList) {
                    if (newList == null || newList.stream().noneMatch(y -> {
                        Long yId = (Long) ReflectHelper.getFieldValueByAnnotation(y, Id.class, null);
                        Long xId = (Long) ReflectHelper.getFieldValueByAnnotation(x1, Id.class, null);
                        return Objects.equals(yId, xId);
                    })) {
                        Long tmpId = (Long) ReflectHelper.getFieldValueByAnnotation(x1, Id.class, null);
                        Optional optional = currentList.stream().filter(m ->
                                ReflectHelper.getFieldValueByAnnotation(m, Id.class, null) == tmpId)
                                .findFirst();
                        if (optional.isPresent()) {
                            ITmtJpaService repo = getValidJpaService(x1.getClass());
                            if (repo != null) {
                                Object obj = optional.get();
                                repo.delete(obj);
                                deletedList.add(obj);
                            }
                        }
                    }
                }
                currentList.removeAll(deletedList);
            }

            //为新增的集合值构建属性对象关系。
            if (newList != null && newList.size() > 0) {
                ITmtJpaService repo = getValidJpaService(newList.get(0).getClass());
                if (repo == null)
                    throw new Exception(String.format("没有配置存储%s类型对象的ITmtJpaService接口实现类",
                            LazyLoadParser.getValidSimpleClassName(newList.get(0).getClass())));

                List<CascadeType> cascadeTypeList = new ArrayList<>();
                OneToMany oneToMany = x.getAnnotation(OneToMany.class);
                if (oneToMany != null) {
                    cascadeTypeList.addAll(Arrays.asList(oneToMany.cascade()));
                }

                for (Object x1 : newList) {
                    if (id != null && id > 0) {
                        //如果是修改，则要保存新增的列表项。
                        Long itemId = (Long) ReflectHelper.getFieldValueByAnnotation(x1, Id.class, null);
                        if (itemId == null && annotation.updateCollection() && !cascadeTypeList.contains(CascadeType.MERGE))
                            repo.create(x1);
                        else if (itemId != null && annotation.updateItemInCollection())
                            repo.update(x1);
                    }
                }
            }
        }
    }

    private void setManyToOneProperty(T object) {
        List<Field> fieldList = ReflectHelper.findAllDeclaredFields(object.getClass());
        fieldList.stream().filter(x -> x.isAnnotationPresent(OneToMany.class)).forEach(x -> {
            //获取列表项的类型。
            Class clazz = ReflectHelper.getActualClassByFieldName(object.getClass(), x.getName());

            //获取列表项类上映射到T类的属性。
            Field mappedField = ReflectHelper.findMappedFieldBy(clazz, ManyToOne.class,
                    object.getClass(), OneToMany.class, x.getName());
            if (mappedField == null)
                return;

            //获取新的集合值。
            List<Object> newList = (List<Object>) ReflectHelper.getFieldValue(object, x.getName());

            //设置关联关系对象。
            if (newList != null && newList.size() > 0)
                newList.forEach(y -> ReflectHelper.setFieldValue(y, mappedField.getName(), object));
        });
    }

    private void retrieveObjectId(T object, Field syncUpdateField, List<Object> newList, List<Object> currentList) {
        if (currentList == null || currentList.size() <= 0 || newList == null || newList.size() <= 0)
            return;

        //获取列表项类上映射到T类的属性。
        Field mappedField = ReflectHelper.findMappedFieldBy(newList.get(0).getClass(), ManyToOne.class,
                object.getClass(), OneToMany.class, syncUpdateField.getName());
        if (mappedField == null)
            return;

        /**
         * 如果object为新增对象，则直接返回
         */
        Long id = (Long) ReflectHelper.getFieldValueByAnnotation(object, Id.class, null);
        if (id == null)
            return;

        //获取列表项类上的所有标记了ManyToOne注解且名称与mappedField名称不同的所有字段。
        List<Field> manyToOneFields = ReflectHelper
                .findAllFieldByAnnotation(newList.get(0).getClass(), ManyToOne.class, 0);
        List<Field> validManyToOneFields = manyToOneFields.stream().filter(y -> !Objects.equals(y.getName(), mappedField.getName()))
                .collect(Collectors.toList());

        newList.forEach(x -> {

            //检查对象的Id属性是否为null, 如果不为null,则直接返回。
            Long xId = (Long) ReflectHelper.getFieldValueByAnnotation(x, Id.class, null);
            if (xId != null)
                return;


            /**
             * 获取第一个比较条件。这个条件肯定可以取到
             */
            Map<String, Long> fieldNameIdMap = new HashMap<>();
            fieldNameIdMap.put(mappedField.getName(), id);

            /**
             * 获取后续比较条件，这个条件不一定能取到。
             */
            //在y对象的manyToOneFields集合中找到属性Id不为null的那个字段。
            validManyToOneFields.stream().forEach(y -> {
                Object propObj = ReflectHelper.getFieldValue(x, y.getName());
                if (propObj != null) {
                    Long propId = (Long) ReflectHelper.getFieldValueByAnnotation(propObj, Id.class, null);
                    if (propId != null) {
                        fieldNameIdMap.put(y.getName(), propId);
                    }
                }
            });

            //必须是多对多关系，才进行ID恢复处理。
            if (fieldNameIdMap.size() < 2)
                return;

            /**
             * 在现有对象列表中过滤出第一条所有比较条件都相等的对象。
             */
            Optional optional = currentList.stream().filter(y -> {
                Boolean[] compareResult = new Boolean[]{true};
                fieldNameIdMap.keySet().forEach(z -> {
                    Object propObj = ReflectHelper.getFieldValue(y, z);
                    Long propId = (Long) ReflectHelper.getFieldValueByAnnotation(propObj, Id.class, null);
                    compareResult[0] = compareResult[0] && Objects.equals(fieldNameIdMap.get(z), propId);
                });
                return compareResult[0];
            }).findFirst();

            //如果找到了相等的对象，则
            if (optional.isPresent()) {
                Object target = optional.get();
                Long targetId = (Long) ReflectHelper.getFieldValueByAnnotation(target, Id.class, null);
                if (targetId != null) {
                    ReflectHelper.invokeMethod(x, "setId", new Long[]{targetId}, new Class[]{Long.class});
                }
            }

        });

    }

    protected ITmtJpaService getValidJpaService(Class<?> clazz) {
        return getValidJpaService(clazz, otherJpaService);
    }

    private ITmtJpaService getValidJpaService(Class<?> clazz, List<ITmtJpaService> jpaServiceList) {
        if (jpaServiceList == null || jpaServiceList.size() <= 0)
            return null;

        Optional optional = jpaServiceList.stream()
                .filter(x -> {
                    Type type = ((Class) (x.getClass().getGenericSuperclass())).getGenericSuperclass();
                    Class<?> tmpClass = (Class<?>) ((ParameterizedType) (type)).getActualTypeArguments()[0];
                    return clazz == tmpClass;
                }).findFirst();

        if (optional.isPresent())
            return (ITmtJpaService) optional.get();

        return null;
    }

}
